package xin.nick.security;


import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jose4j.jwt.JwtClaims;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import xin.nick.config.SystemProperties;
import xin.nick.constant.SystemConstants;
import xin.nick.entity.LoginUser;
import xin.nick.manager.SystemUserManager;
import xin.nick.util.IpUtil;
import xin.nick.util.JwtUtil;
import xin.nick.util.QueryUtil;
import xin.nick.util.UserIdUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * token过滤器 验证token有效性
 *
 * @author Nick
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenAuthenticationTokenFilter extends OncePerRequestFilter {

    private final RedisTemplate<String, Object> redisTemplate;
    private final SystemProperties systemProperties;
    private final SystemUserManager systemUserManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        // 放行登录和验证码
        String requestURI = request.getRequestURI();
        if (SystemConstants.CODE_PATH.equals(requestURI)
                || SystemConstants.LOGIN_PATH.equals(requestURI)
                || SystemConstants.ERROR_PATH.equals(requestURI)) {
            chain.doFilter(request, response);
            return;
        }

        String token = QueryUtil.getToken(request);
        Long userId = null;
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (Objects.isNull(authentication)) {
            // 检测token并获取 LoginUser
            LoginUser loginUser = checkTokenAndGetLoginUser(token);
            if (Objects.nonNull(loginUser)) {
                // 将用户信息放入 context
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }

        } else {
            // 但是这个防止出现自定义的 authentication 没抛异常而且是null
            // 所以加了一个判断
            Object principal = authentication.getPrincipal();
            if (Objects.nonNull(principal)) {
                if (principal instanceof LoginUser) {
                    userId = ((LoginUser) principal).getUserId();
                }
            }

        }
        // 设置全局manager的 userID
        UserIdUtil.setUserId(userId);

        log.info("tokenFilter:, userId: {}, ip: {}, uri: {}, token: {}", userId,
                IpUtil.getIpAddress(request),
                request.getRequestURI(),
                token);

        chain.doFilter(request, response);

        UserIdUtil.removeUserId();
    }

    /**
     * 检查并获取userId
     * @param token token
     * @return Long
     */
    public LoginUser checkTokenAndGetLoginUser(String token) {

        // 用户未登录
        if (StrUtil.isBlank(token)) {
            return null;
        }

        try {
            // 通过jwt获取本次登录的 userId
            JwtClaims jwtClaims = JwtUtil.getJwtClaims(token, systemProperties.getJwtSecret());
            String subject = jwtClaims.getSubject();
            AES aes = SecureUtil.aes(systemProperties.getAesSecret().getBytes());
            String userIdString = aes.decryptStr(subject);
            Long userId = Long.parseLong(userIdString);

            // 检查Redis中token和本次的是否相同
            String tokenKey = SystemConstants.USER_TOKEN_KEY + userIdString;
            String redisTokenString = Objects.requireNonNull(redisTemplate.opsForValue().get(tokenKey)).toString();
            if (StrUtil.equals(token, redisTokenString)) {
                // 相同则重置用户id过期时间,并返回
                redisTemplate.expire(tokenKey, SystemConstants.USER_TOKEN_EXPIRE, TimeUnit.SECONDS);

                // 获取用户信息
                LoginUser loginUser = systemUserManager.getLoginUserByUserId(userId);
                loginUser.setToken(token);
                // 根据用户获取权限信息
                List<SimpleGrantedAuthority> authorityList = getAuthorityList(userId);
                loginUser.setAuthorities(authorityList);

                return loginUser;
            }


        } catch (Exception e) {
            log.error("登录token校验异常",e);
        }
        return null;
    }

    /**
     * 因为security自身的设计原因，我们在用户分组和角色权限，增加ROLE前缀
     *
     * @param role 角色
     * @return SimpleGrantedAuthority
     */
    private SimpleGrantedAuthority genSimpleGrantedAuthority(String role) {
        if (!role.startsWith(SystemConstants.ROLE_PREFIX)) {
            role = SystemConstants.ROLE_PREFIX + role;
        }
        return new SimpleGrantedAuthority(role);
    }


    /**
     * 根据管理员id获取权限列表
     * @param userId Long
     * @return List<String>
     */
    private List<SimpleGrantedAuthority> getAuthorityList(Long userId) {

        List<SimpleGrantedAuthority> allAuthorityList = new ArrayList<>();

        // 添加超管角色
        if (systemUserManager.isRoot(userId)) {
            allAuthorityList.add(new SimpleGrantedAuthority(SystemConstants.ROOT_ROLE));
        }

        return allAuthorityList;
    }
}
